package io.github.lhdxhl.wechatpay.v3;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import io.github.lhdxhl.wechatpay.enums.WxPayV3Type;
import io.github.lhdxhl.wechatpay.exception.PayException;
import io.github.lhdxhl.wechatpay.property.WxPayProperties;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.PrivateKey;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;

/**
 * @author lsk
 * @version 2.0.0
 * @ClassName WxPayClient.java
 * @Description 微信支付客户端
 * @createTime 2022年09月22日 18:31:00
 */
public class WxPayClient {

    /**
     * the pay httpClient
     */
    private final CloseableHttpClient httpClient;

    /**
     * the pay properties
     */
    private final WxPayProperties wxPayProperties;

    /**
     * 获取当前配置文件
     * @return
     */
    public WxPayProperties getConfig(){
        return this.wxPayProperties;
    }

    /**
     * 获取当前请求http client
     * @return
     */
    public CloseableHttpClient getHttpClient(){
        return this.httpClient;
    }


    public WxPayClient(CloseableHttpClient httpClient, WxPayProperties wxPayProperties){
        this.httpClient= httpClient;
        this.wxPayProperties = wxPayProperties;
    }

    public <M> Executor<M> withType(WxPayV3Type wxPayV3Type, M m) {
        return new Executor<>(wxPayV3Type, m, this.httpClient, this.wxPayProperties);
    }


    public static class Executor<M> {

        /**
         * The V3 pay type.
         */
        private final WxPayV3Type wxPayV3Type;

        /**
         * The Model.
         */
        private final M model;


        /**
         * the pay httpClient
         */
        private final CloseableHttpClient httpClient;

        /**
         * the pay properties
         */
        private final WxPayProperties wxPayProperties;



        /**
         * The Request entity bi function.
         */
        private BiFunction<WxPayV3Type, M, HttpUriRequest> requestEntityBiFunction;

        Executor(WxPayV3Type wxPayV3Type, M model,CloseableHttpClient httpClient, WxPayProperties wxPayProperties ) {
            this. wxPayV3Type= wxPayV3Type;
            this.model = model;
            this.httpClient = httpClient;
            this.wxPayProperties = wxPayProperties;
        }

        /**
         * The Response body consumer.
         */
        private Consumer<WxResponseEntity> responseBodyConsumer;

        public Executor<M> function(BiFunction<WxPayV3Type, M,HttpUriRequest> requestEntityBiFunction) {
            this.requestEntityBiFunction = requestEntityBiFunction;
            return this;
        }


        /**
         * @title consumer
         * @description 代理消费者
         * @author lsk
         * @param: responseBodyConsumer
         * @updateTime 2022/10/4 23:59
         * @return: io.github.lhdxhl.wechatpay.v3.WxPayClient.Executor<M>
         */
        public Executor<M> consumer(Consumer<WxResponseEntity> responseBodyConsumer) {
            this.responseBodyConsumer = responseBodyConsumer;
            return this;
        }



        /**
         * @title request
         * @description 发起接口 request
         * @author lsk
         * @updateTime 2022/10/5 16:41
         * @throws IOException
         */
        public <T> void request() throws IOException {
            HttpUriRequest request = this.requestEntityBiFunction.apply(this.wxPayV3Type, this.model);
            WxRequestEntity  wxRequestEntity = WxRequestEntity.of(request, this.responseBodyConsumer);
            this.doExecute(wxRequestEntity);
        }

        /**
         * @title download
         * @description  下载文件返回的字符串数据集
         * @author lsk
         * @updateTime 2022/10/5 16:39
         * @return: java.lang.String
         * @throws IOException
         */
        public String download() throws IOException {
            HttpUriRequest request = this.requestEntityBiFunction.apply(this.wxPayV3Type, this.model);
            WxRequestEntity  wxRequestEntity = WxRequestEntity.of(request, this.responseBodyConsumer);
            return this.doDownload(wxRequestEntity);
        }

        /**
         * @title resource
         * @description 下载数据返回流
         * @author lsk
         * @updateTime 2022/10/5 16:43
         * @return: io.github.lhdxhl.wechatpay.v3.WxResponseEntity<org.springframework.core.io.Resource>
         * @throws IOException
         */
        protected WxResponseEntity<Resource> resource() throws IOException {
            HttpUriRequest request = this.requestEntityBiFunction.apply(this.wxPayV3Type, this.model);
            WxRequestEntity  wxRequestEntity = WxRequestEntity.of(request, this.responseBodyConsumer);
            return this.doResource(wxRequestEntity);
        }


        /**
         * @title doExecute
         * @description 执行当前接口请求
         * @author lsk
         * @param: requestEntity
         * @updateTime 2022/10/5 16:40
         * @throws
         */
        private <T> void doExecute(WxRequestEntity requestEntity) throws IOException {
            WxResponseEntity responseEntity = new WxResponseEntity();
            CloseableHttpResponse  response = this.httpClient.execute(requestEntity.getRequest());
            try {
                int statusCode = response.getStatusLine().getStatusCode();
                responseEntity.setHttpStatus(statusCode);
                String body = EntityUtils.toString(response.getEntity());
                System.out.println("wechat pay response body:" + body);
                ObjectMapper objectMapper = new ObjectMapper();
                ObjectNode  objectNode = null;
                if(Objects.isNull(body) || StringUtils.isEmpty(body)){
                    objectNode = objectMapper.createObjectNode();
                }else {
                    objectNode = objectMapper.readValue(body, ObjectNode.class);
                }
                responseEntity.setBody(objectNode);
                if (!responseEntity.is2xxSuccessful()){
                    PayException payException = new PayException("wechat pay server error , statusCode :" + statusCode + ",result : " +  EntityUtils.toString(response.getEntity()));
                    payException.setResponse(responseEntity);
                    throw payException;
                }
                requestEntity.getResponseBodyConsumer().accept(responseEntity);
            }catch (Exception e){
                System.out.println("wechat pay server error :" + e);
                throw e;
            }finally {
                if(Objects.nonNull(response)){
                    response.close();
                }
            }
        }


        /**
         * @title doDownload
         * @description 下载文件返回的字符串数据集
         * @author lsk
         * @param: requestEntity
         * @updateTime 2022/10/5 16:20
         * @return: java.lang.String
         * @throws IOException
         */
        private <T> String doDownload(WxRequestEntity requestEntity) throws IOException {
            WxResponseEntity<String> responseEntity = new WxResponseEntity();
            WxPayProperties config = this.wxPayProperties;
            //请求URL
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getMchPrivateKey().getBytes("utf-8")));

            //初始化httpClient
            //该接口无需进行签名验证、通过withValidator((response) -> true)实现
            CloseableHttpClient httpClient1 =  WechatPayHttpClientBuilder.create().withMerchant(config.getMchId(), config.getMchSerialNumber(), merchantPrivateKey).withValidator((response) -> true).build();
            CloseableHttpResponse  response =httpClient1.execute(requestEntity.getRequest());
            try {
                int statusCode = response.getStatusLine().getStatusCode();
                responseEntity.setHttpStatus(statusCode);
                responseEntity.setBody(EntityUtils.toString(response.getEntity()));
                if (!responseEntity.is2xxSuccessful()) {
                    PayException payException = new PayException("wechat pay download error, statusCode " + statusCode + ",result : " + responseEntity);
                    payException.setResponse(responseEntity);
                    throw payException;
                }
                return Optional.ofNullable(responseEntity.getBody()).orElse("");
            }catch (Exception e){
                System.out.println("wechat pay download error :" + e);
                throw e;
            }finally {
                if(Objects.nonNull(response)){
                    response.close();
                }
            }
        }



        /**
         * @title doResource
         * @description 下载文件返回数据流
         * @author lsk
         * @param: requestEntity
         * @updateTime 2022/10/5 16:19
         * @return: io.github.lhdxhl.wechatpay.v3.WxResponseEntity<org.springframework.core.io.Resource>
         * @throws IOException
         */
        private <T> WxResponseEntity<Resource> doResource(WxRequestEntity requestEntity) throws IOException {

            WxResponseEntity<Resource> responseEntity = new WxResponseEntity();
            WxPayProperties config = this.wxPayProperties;
            //请求URL
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getMchPrivateKey().getBytes("utf-8")));

            //初始化httpClient
            //该接口无需进行签名验证、通过withValidator((response) -> true)实现
            CloseableHttpClient httpClient1 =  WechatPayHttpClientBuilder.create().withMerchant(config.getMchId(), config.getMchSerialNumber(), merchantPrivateKey).withValidator((response) -> true).build();
            CloseableHttpResponse  response =httpClient1.execute(requestEntity.getRequest());
            try {
                int statusCode = response.getStatusLine().getStatusCode();
                responseEntity.setHttpStatus(statusCode);
                responseEntity.setBody((Resource) response.getEntity().getContent());
                if (!responseEntity.is2xxSuccessful()) {
                    PayException payException = new PayException("wechat pay resource error, statusCode " + statusCode + ",result : " + responseEntity);
                    payException.setResponse(responseEntity);
                    throw payException;
                }
                return responseEntity;
            }catch (Exception e){
                System.out.println("wechat pay download error :" + e);
                throw e;
            }finally {
                if(Objects.nonNull(response)){
                    response.close();
                }
            }
        }
    }





}
